package top.mcwebsite.markdown.render

import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.LinkInteractionListener
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.withLink
import androidx.compose.ui.text.withStyle
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.CompositeASTNode
import org.intellij.markdown.ast.LeafASTNode
import org.intellij.markdown.ast.findChildOfType
import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.flavours.gfm.GFMElementTypes
import org.intellij.markdown.flavours.gfm.GFMTokenTypes
import top.mcwebsite.markdown.LinkDestination
import top.mcwebsite.markdown.MarkdownEvent
import top.mcwebsite.markdown.MarkdownInternEvent
import top.mcwebsite.markdown.MarkdownLinkType
import top.mcwebsite.markdown.MarkdownState
import top.mcwebsite.markdown.theme.LocalMarkdownTheme
import top.mcwebsite.markdown.theme.MarkdownTheme
import top.mcwebsite.markdown.utils.toStateMap


@Composable
fun MarkdownText(
    content: String,
    astNode: ASTNode,
    markdownState: MarkdownState,
    textStyle: TextStyle,
    modifier: Modifier = Modifier
) {
    val theme = LocalMarkdownTheme.current
    val annotatedString = remember(astNode) {
        buildAnnotatedString(content, astNode, markdownState, theme)
    }

    val layoutResult = remember(astNode) {
        mutableStateOf<TextLayoutResult?>(null)
    }
    val imageStates = remember(annotatedString) {
        annotatedString.getImageStates().toStateMap()
    }
    BasicText(
        annotatedString,
        style = textStyle,
        inlineContent = annotatedString.getInlineImage(imageStates),
        onTextLayout = {
            layoutResult.value = it
        },
        modifier = modifier
    )
}

fun buildAnnotatedString(context: String, astNode: ASTNode, markdownState: MarkdownState, theme: MarkdownTheme): AnnotatedString {
    val builder = AnnotatedString.Builder()
    parseASTNode(astNode, context, builder, markdownState, theme)
    return builder.toAnnotatedString()
}

fun parseASTNode(
    astNode: ASTNode,
    context: String,
    builder: AnnotatedString.Builder,
    state: MarkdownState,
    theme: MarkdownTheme
) {
    astNode.children.forEach { childNode ->
        if (childNode is LeafASTNode) {
            parseLeafNode(childNode, context, builder, state, theme)
        } else if (childNode is CompositeASTNode) {
            parseCompositeNode(childNode, context, builder, state, theme)
        }
    }
}

private fun parseLeafNode(
    astNode: LeafASTNode,
    context: String,
    builder: AnnotatedString.Builder,
    state: MarkdownState,
    theme: MarkdownTheme,
) {
    when (astNode.type) {
        MarkdownTokenTypes.TEXT -> {
            println(astNode.getTextInNode(context))
            builder.append(astNode.getTextInNode(context))
        }

        MarkdownTokenTypes.WHITE_SPACE -> {
            if (builder.length != 0) {
                builder.append(" ")
            }
        }

        MarkdownTokenTypes.SINGLE_QUOTE -> {
            builder.append("'")
        }

        MarkdownTokenTypes.DOUBLE_QUOTE -> {
            builder.append("\"")
        }

        MarkdownTokenTypes.LPAREN -> {
            builder.append("(")
        }

        MarkdownTokenTypes.RPAREN -> {
            builder.append(")")
        }

        MarkdownTokenTypes.LBRACKET -> {
            builder.append("[")
        }

        MarkdownTokenTypes.RBRACKET -> {
            builder.append("]")
        }

        MarkdownTokenTypes.LT -> {
            builder.append("<")
        }

        MarkdownTokenTypes.GT -> {
            builder.append(">")
        }

        MarkdownTokenTypes.COLON -> {
            builder.append(":")
        }

        MarkdownTokenTypes.EXCLAMATION_MARK -> {
            builder.append("!")
        }

        MarkdownTokenTypes.HARD_LINE_BREAK -> {
            builder.append("\n\n")
        }

        MarkdownTokenTypes.AUTOLINK -> {
            builder.append(astNode.getTextInNode(context))
        }

        MarkdownTokenTypes.EMAIL_AUTOLINK -> {
            val link = astNode.getTextInNode(context).toString()
            state.addLinkDestination(link, LinkDestination(MarkdownLinkType.EMAIL, link, null))
            builder.withLink(
                link = LinkAnnotation.Clickable(link, styles = theme.linkStyles, linkInteractionListener = {
                    state.onEvent(MarkdownEvent.Email(link))
                })
            ) {
                builder.append(link)
            }

        }

        GFMTokenTypes.GFM_AUTOLINK -> {
            val link = astNode.getTextInNode(context).toString()
            state.addLinkDestination(link, LinkDestination(MarkdownLinkType.AUTO, link, null))
            builder.withLink(
                link = LinkAnnotation.Url(link, styles = theme.linkStyles)
            ) {
                builder.append(link)
            }
        }
    }
}

private fun parseCompositeNode(
    astNode: CompositeASTNode,
    content: String,
    builder: AnnotatedString.Builder,
    state: MarkdownState,
    theme: MarkdownTheme,
) {
    when (astNode.type) {
        MarkdownElementTypes.STRONG -> {
            builder.withStyle(theme.boldStyle) {
                parseASTNode(astNode, content, builder, state, theme)
            }
        }

        MarkdownElementTypes.EMPH -> {
            builder.withStyle(theme.italicStyle) {
                parseASTNode(astNode, content, builder, state, theme)
            }
        }

        MarkdownElementTypes.CODE_SPAN -> {
            builder.withStyle(theme.codeSpanStyle) {
                builder.append(" ")
                parseASTNode(astNode, content, builder, state, theme)
                builder.append(" ")
            }
        }

        MarkdownElementTypes.IMAGE -> {
            val inlineLinkNode = astNode.findChildOfType(MarkdownElementTypes.INLINE_LINK)
            val link = inlineLinkNode
                ?.findChildOfType(MarkdownElementTypes.LINK_TEXT)
                ?.findChildOfType(MarkdownTokenTypes.TEXT)
                ?.getTextInNode(content)
            val destination = inlineLinkNode
                ?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
                ?.getTextInNode(content)
            if (link == null || destination.isNullOrEmpty()) {
                parseASTNode(astNode, content, builder, state, theme)
            } else {
                builder.appendInlineContent("$IMAGE_ID_PREFIX $destination $link", destination.toString())
            }
        }

        MarkdownElementTypes.INLINE_LINK -> {
            val destination = parseLinkDestination(astNode, content)
            val linkTitle = parseLinkTitle(astNode, content)
            if (destination == null) {
                parseLinkText(
                    astNode.findChildOfType(MarkdownElementTypes.LINK_TEXT),
                    content,
                    builder,
                    state,
                    theme,
                    false
                )
            } else {
                val markdownLinkType = parseDestinationType(destination)
                val linkIdentifier = state.getLinkIdentifier(destination)
                state.addLinkDestination(linkIdentifier, LinkDestination(markdownLinkType, destination, linkTitle))
                val listener = if (markdownLinkType == MarkdownLinkType.Anchor) {
                    LinkInteractionListener {
                        state.event.value = MarkdownInternEvent.OnClickAnchor(destination)
                    }
                } else null
                builder.withLink(link = LinkAnnotation.Url(destination, styles = theme.linkStyles, linkInteractionListener = listener)) {
                    parseLinkText(
                        astNode.findChildOfType(MarkdownElementTypes.LINK_TEXT),
                        content, builder, state, theme, true
                    )
                }
            }
        }

        MarkdownElementTypes.SHORT_REFERENCE_LINK -> {
            val linkLabel = parseLinkLabel(astNode, content)
            val destination = if (linkLabel != null) state.getLinkDestination(linkLabel) else null
            if (destination == null) {
                parseASTNode(astNode, content, builder, state, theme)
            } else {
                builder.withLink(link = LinkAnnotation.Url(destination.link, styles = theme.linkStyles)) {
                    builder.append(linkLabel!!.removeSurrounding("[", "]"))
                }
            }
        }

        MarkdownElementTypes.FULL_REFERENCE_LINK -> {
            val linkLabel = parseLinkLabel(astNode, content)
            // 检测是否存在 linkLabel
            val destination = if (linkLabel != null) state.getLinkDestination(linkLabel) else null
            if (destination == null) {
                parseASTNode(astNode, content, builder, state, theme)
            } else {
                builder.withLink(link = LinkAnnotation.Url(destination.link, styles = theme.linkStyles)) {
                    parseLinkText(
                        astNode.findChildOfType(MarkdownElementTypes.LINK_TEXT),
                        content, builder, state, theme, true
                    )
                }
            }
        }

        // 如果匹配到这里，那么说明是一个不正确的格式，按正常文本处理
        MarkdownElementTypes.LINK_LABEL -> {
            parseASTNode(astNode, content, builder, state, theme)
        }

        MarkdownElementTypes.LINK_TEXT -> {
            parseASTNode(astNode, content, builder, state, theme)
        }

        MarkdownElementTypes.AUTOLINK -> {
            val autoLink = astNode.findChildOfType(MarkdownTokenTypes.AUTOLINK)
                ?.getTextInNode(content)?.toString()
            if (autoLink == null) {
                parseASTNode(astNode, content, builder, state, theme)
            } else {
                state.addLinkDestination(autoLink, LinkDestination(MarkdownLinkType.AUTO, autoLink, null))
                builder.withLink(link = LinkAnnotation.Url(autoLink, styles = theme.linkStyles)) {
                    builder.append(autoLink)
                }
            }
        }

        GFMElementTypes.STRIKETHROUGH -> {
            builder.withStyle(theme.strikethroughStyle) {
                parseASTNode(astNode, content, builder, state, theme)
            }
        }
    }
}

/**
 * 处理 LINK_TEXT
 */
private fun parseLinkText(
    astNode: ASTNode?,
    context: String,
    builder: AnnotatedString.Builder,
    state: MarkdownState,
    theme: MarkdownTheme,
    hasDestination: Boolean
) {
    astNode ?: return
    val children = if (hasDestination) {
        astNode.children.drop(1).dropLast(1)
    } else {
        astNode.children
    }
    children.forEach { childNode ->
        if (childNode is LeafASTNode) {
            parseLeafNode(childNode, context, builder, state, theme)
        } else if (childNode is CompositeASTNode) {
            parseCompositeNode(childNode, context, builder, state, theme)
        }
    }
}


fun parseLinkDestination(astNode: ASTNode, text: String): String? {
    val linkDestinationNode = astNode.findChildOfType(MarkdownElementTypes.LINK_DESTINATION) ?: return null
    var linkDestination: String? = null
    linkDestinationNode.children.forEach {
        if (it.type == MarkdownTokenTypes.TEXT) {
            linkDestination = text.substring(linkDestinationNode.startOffset, linkDestinationNode.endOffset)
        }
        if (it.type == GFMTokenTypes.GFM_AUTOLINK) {
            linkDestination = text.substring(linkDestinationNode.startOffset, linkDestinationNode.endOffset)
        }
    }
    return linkDestination
}


private fun parseDestinationType(context: String): MarkdownLinkType {
    return if (context.startsWith("#")) {
        MarkdownLinkType.Anchor
    } else {
        MarkdownLinkType.Normal
    }
}

private fun parseLinkLabel(astNode: ASTNode, content: String): String? {
    return astNode.findChildOfType(MarkdownElementTypes.LINK_LABEL)
        ?.getTextInNode(content)?.toString()
}

private fun parseLinkTitle(astNode: ASTNode, context: String): String? {
    return astNode.findChildOfType(MarkdownElementTypes.LINK_TITLE)
        ?.getTextInNode(context)?.toString()?.removeSurrounding("\"")
}